/********************************************************************************
* @file    oled_core.c
* @author  jianqiang.xue
* @version V1.0.0
* @date    2021-08-07
* @brief   参考：https://www.cnblogs.com/Gimiracle/p/13520991.html
* 笔记：
* 在得到应答后，发送一个控制字节，由Co位和D/C位及尾部000000组成。
* D7 D6   D5 D4 D3 D2 D1 D0
* Co D/C# 0  0  0  0  0  0
* a）Co位为0，则后续字节均为数据。D/C#位确定下一个数据字节作为命令或数据。
* b）D/C#位设置为“0”，后续数据字节定义为命令。 D/C#位设置为“1” ，后续数据字节定义为数据，并存储在GDDRAM上。
*   每次数据写入后，GDDRAM列地址指针将自动增加一个。
********************************************************************************/
/* Includes ------------------------------------------------------------------*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

#include "bsp_i2c.h"
/* Private includes ----------------------------------------------------------*/
#include "oled_core.h"
#include "SSD1306.h"
#include "os_api.h"
/* Private define ------------------------------------------------------------*/
#define FUNC_CMD  0x00
#define FUNC_DATA 0x40
/* Private typedef -----------------------------------------------------------*/
static const oled_cfg_t g_oled_info =
{
    .i2c_bus  = I2C_BUS0,
    .dev_addr = 0x78,
    .x_max    = 128,
    .y_max    = 64,
};
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
static bool g_oled_state = false;

static const uint8_t g_cmd_oled_init_cfg[] = {
    FUNC_CMD, /*命令类*/
    0xAE, /*关闭oled面板*/
    0x00, /*设置低列地址*/
    0x10, /*设置高列地址*/
    0x40, /*设置起始行地址*/

    0x81, /*对比度设置，可设置亮度*/
    0XFF, /*对比度值0-255*/

    0XA1, /*设置分段/列映射     0xa0左右反置 0xa1正常*/
    0XC8, /*设置COM/行扫描方向   0xc0上下反置 0xc8正常*/
    0XA6, /*设置正常显示 0xa7逆显示*/

    0XA8, /*设置多路复用比率（1到64）*/
    0X3F, /*1/64负荷*/

    0XD3, /*设置显示偏移移位映射RAM计数器*/
    0x00, /*RAM计数器值：0x00~0x3F*/

    0XD5, /*设置显示分时比/振荡器频率*/
    0X80, /*设置分频比，将时钟设置为100帧/秒(默认值)*/

    0XD9, /*设置 预充电周期*/
    0XF1, /*预充电设为15时钟，放电设为1时钟*/

    0XDA, /*设置com引脚硬件配置*/
    0X12, /*默认值*/

    0XDB, /*设置 共通电位 (高)*/
    0X40, /*设置VCOM取消选择级别*/

    0X20, /*设置页面寻址模式（0x00/0x01/0x02）*/
    0X02, /*默认值*/

    0X8D, /*设置预充电 启用/禁用*/
    0X14, /*set(0x10) disable*/

    0XA4, /*A4h，X0 =0b：恢复 RAM内容的显示（RESET）*/
    0XA6, /*A6h, X[0]=0b:正常显示（RESET）*/
};

static const uint8_t g_cmd_oled_open_display[] = {
    FUNC_CMD, /*命令类*/
    0X8D, /*设置 共通电位 (高)*/
    0X14,
    0XAF  /*显示开，正常模式 */
};

static const uint8_t g_cmd_oled_close_display[] = {
    FUNC_CMD, /*命令类*/
    0X8D, /*设置 共通电位 (高)*/
    0X14,
    0XAE  /*显示关 （睡眠模式）*/
};

static const uint8_t g_cmd_oled_null[129] = {FUNC_DATA, 0};

static uint8_t g_oled_data_buff[128] = {0};
/* Public function prototypes -----------------------------------------------*/

/**
 * @brief  oled初始化
 * @note   由于单片机上电初始化比OLED快，所以必须加上延迟，等待OLED上复位完成，约100-200ms
 * @retval None
 */
uint8_t oled_init(void)
{
    return bsp_i2c_write_nbyte(g_oled_info.i2c_bus, g_oled_info.dev_addr,
                               (uint8_t *)g_cmd_oled_init_cfg, sizeof(g_cmd_oled_init_cfg));
}

/**
 * @brief  得到当前状态
 * @note   NULL
 * @retval 0--空闲 1--忙
 */
bool oled_get_state(void)
{
    return g_oled_state;
}

uint8_t oled_get_x_max(void)
{
    return g_oled_info.x_max;
}

uint8_t oled_get_y_max(void)
{
    return g_oled_info.y_max;
}

void oled_cls(void)
{
    if (g_oled_state)
    {
        return;
    }
    g_oled_state = true;
    uint8_t data[] = {FUNC_CMD, 0xb0, 0x00, 0x10};
    for (uint8_t i = 0; i < g_oled_info.y_max / 8; i++)
    {
        data[1] = 0xb0 + i;
        bsp_i2c_write_nbyte(g_oled_info.i2c_bus, g_oled_info.dev_addr,
                            data, sizeof(data));
        bsp_i2c_write_nbyte(g_oled_info.i2c_bus, g_oled_info.dev_addr,
                            (uint8_t *)g_cmd_oled_null, sizeof(g_cmd_oled_null));
    }
    g_oled_state = false;
}

void oled_clear(uint8_t x, uint8_t y, uint8_t x1, uint8_t y1)
{
    if (g_oled_state)
    {
        return;
    }
    g_oled_state = true;
    uint8_t data[] = {FUNC_CMD, 0xb0, 0x00, 0x10};
    for (uint8_t i = y; i < y1; i++)
    {
        data[1] = 0xb0 + i;
        data[2] = (x & 0x0F);
        data[3] = ((x & 0xF0) >> 4) | 0x10;
        bsp_i2c_write_nbyte(g_oled_info.i2c_bus, g_oled_info.dev_addr,
                            data, sizeof(data));
        bsp_i2c_write_nbyte(g_oled_info.i2c_bus, g_oled_info.dev_addr,
                            (uint8_t *)g_cmd_oled_null, x1-x);
    }
    g_oled_state = false;
}

uint8_t oled_open_display(void)
{
    return bsp_i2c_write_nbyte(g_oled_info.i2c_bus, g_oled_info.dev_addr,
                               (uint8_t *)g_cmd_oled_open_display, sizeof(g_cmd_oled_open_display));
}

uint8_t oled_close_display(void)
{
    return bsp_i2c_write_nbyte(g_oled_info.i2c_bus, g_oled_info.dev_addr,
                               (uint8_t *)g_cmd_oled_close_display, sizeof(g_cmd_oled_close_display));
}

uint8_t oled_set_pos(uint8_t x, uint8_t y)
{
    uint8_t data[] = {FUNC_CMD, 0xb0, 0x00, 0x00};
    data[1] += y;
    data[2] = ((x & 0xF0) >> 4) | 0x10;
    data[3] = (x & 0x0F);
    return bsp_i2c_write_nbyte(g_oled_info.i2c_bus, g_oled_info.dev_addr,
                               data, sizeof(data));
}

uint8_t oled_write_data(uint8_t *data, uint16_t len)
{
    if (g_oled_state)
    {
        return OS_EVENT_TIMEOUT;
    }
    g_oled_state = false;

    memset(g_oled_data_buff, 0, len + 1);
    g_oled_data_buff[0] = FUNC_DATA;
    memcpy(g_oled_data_buff + 1, data, len);
    return bsp_i2c_write_nbyte(g_oled_info.i2c_bus, g_oled_info.dev_addr,
                               g_oled_data_buff, len+1);
}

/* Private function prototypes -----------------------------------------------*/

